feat(clients/js): add confidential mint/burn instruction plan helpers - #1357
feat(clients/js): add confidential mint/burn instruction plan helpers#1357eldarik wants to merge 3 commits into
Conversation
Token-2022's /confidential subpath ships InstructionPlan helpers for transfer, withdraw and configure, but nothing for confidential mint/burn — downstream consumers had to re-derive the three-proof orchestration themselves. Add helpers so that logic lives upstream. - getConfidentialMintInstructionPlan / getConfidentialBurnInstructionPlan: build the equality, batched grouped-3-handles validity and U128 range proofs, verify them via context-state accounts, and assemble the token instruction — reusing the existing transfer/withdraw plumbing. - getUpdateConfidentialMintBurnDecryptableSupplyInstructionFromSupply: re-encrypt the decryptable supply under the supply AES key (u64-guarded). - addWithLoHiCiphertexts: the one missing arithmetic primitive, mirroring subtractWithLoHiCiphertexts, for homomorphically adding the minted amount to the encrypted supply. - test setup: createConfidentialMintBurnMint, initializing a mint with both the ConfidentialTransferMint and ConfidentialMintBurn extensions. The helpers reuse the primitives introduced by solana-program#1253 rather than adding their own: getRequiredMintExtension for the ConfidentialMintBurn lookup and assertU64Amount for the supply guards. getAuditorElGamalPubkey is widened to a structural input so transfer, transfer-with-fee, mint and burn all resolve the auditor key through one function, and main's now-redundant getRequiredConfidentialTransferMintExtension folds into the generic lookup. Covered by a new offline arithmetic test and a validator e2e that exercises mint -> apply -> burn -> apply-pending-burn -> update-supply against the on-chain ZK verifiers.
Extract the shared proof setup/cleanup/args from getConfidentialBurnInstructionPlan into buildConfidentialBurnProofPlan, then add getPermissionedConfidentialBurnInstructionPlan on top of it. The two variants compute identical proofs and differ only in the middle instruction and its signer set: the standard variant carries multiSigners, while the permissioned variant has the mint's configured permissionedBurnAuthority co-sign alongside the account owner. token-2022 rejects the standard ConfidentialBurn instruction on mints carrying the PermissionedBurn extension, so this variant is required for such mints (as tokenized-security mints use). Support the case in the test harness by letting createConfidentialMintBurnMint optionally attach the PermissionedBurn extension, and add an integration test covering a confidential burn on a permissioned mint.
5f375a4 to
43f8c74
Compare
|
@lorisleiva since we closed #1338 I've created a new PR with re-using helpers from #1253. Could you please take a look? |
ReviewVerified the proof construction against
Guards look right too: Bug: permissioned burn silently drops
|
GetPermissionedConfidentialBurnInstructionPlanInput extends the standard burn input and so inherits multiSigners, but the wrapper only forwarded permissionedBurnAuthority — the shared burnArgs deliberately exclude multiSigners and only the standard variant added them back. A caller burning from a multisig-owned account therefore had its signers silently dropped, producing an instruction that fails on-chain with MissingRequiredSignature and no type error to catch it. The generated getPermissionedConfidentialBurnInstruction does accept multiSigners, so forward them. Cover the case with a validator e2e that burns from an account owned by a 2-of-2 multisig via the permissioned variant; it fails with MissingRequiredSignature without the fix. Supporting it needed createMultisig in the test harness and a widened createConfidentialTokenAccount that accepts an address owner plus multiSigners. Also, from the same review pass: - Document getConfidentialMintInstructionPlan's supply-sync precondition: the equality proof ties confidentialSupply to AES_decrypt(decryptableSupply), so drift (e.g. after ApplyPendingBurn) makes the proof fail on-chain — point callers at the update helper. - Factor the fields shared by the mint and burn plan inputs into GetConfidentialMintBurnInstructionPlanBaseInput. - Move the duplicated getConfidentialMintBurnExtension and decryptable-supply assertion block out of both mint/burn e2e tests into _setup as fetchDecryptableSupply.
|
@gitteri thanks for the review. I've addressed the feedback. |
Adds the confidential mint/burn path to the
/confidentialsubpath, which today shipsInstructionPlanhelpers for transfer, withdraw and configure but nothing forConfidentialMintBurn— leaving downstream consumers to re-derive the three-proof orchestration themselves.Supersedes #1338, which I closed after mistakenly agreeing it duplicated #1253. It does not: #1253 added the fee-aware transfer path (
TransferFeeConfig+ConfidentialTransferFee, five proofs, U256 range), and contains noConfidentialMintBurnhandling. This PR is rebased ontomainnow that #1253 has landed, and builds on its primitives rather than re-introducing its own.What's added
getConfidentialMintInstructionPlan/getConfidentialBurnInstructionPlan— build the equality, batched grouped-3-handles validity and U128 range proofs, verify them via context-state accounts, and assemble the token instruction, reusing the existing transfer/withdraw plumbing (buildContextStateProofPlan).getPermissionedConfidentialBurnInstructionPlan— the permissioned burn variant. token-2022 rejects the standardConfidentialBurnon mints carrying thePermissionedBurnextension, so this is required for those mints. Identical proofs; the mint's configuredpermissionedBurnAuthorityco-signs alongside the account owner.getUpdateConfidentialMintBurnDecryptableSupplyInstructionFromSupply— re-encrypts the decryptable supply under the supply AES key. The ElGamal supply and the AES decryptable supply can drift (e.g.ApplyPendingBurnadvances the former but cannot re-encrypt the latter), so the authority uses this to re-assert it.addWithLoHiCiphertextsinconfidentialTransferArithmetic.ts— the one missing arithmetic primitive, mirroring the existingsubtractWithLoHiCiphertexts, for homomorphically adding the minted amount to the encrypted supply.Reusing #1253's primitives
#1338 and #1253 both incidentally grew their own copies of two small helpers. Rather than reintroduce mine, this PR adopts #1253's and converges the file on one of each:
getRequiredConfidentialMintBurnExtensionforgetRequiredMintExtension(mintAccount, 'ConfidentialMintBurn'). While there,getRequiredConfidentialTransferMintExtensionfolds into the same generic, since that is what the generic was written to replace.resolveAuditorElGamalPubkeyand widened the existing asyncgetAuditorElGamalPubkeyto a structural input, so transfer, transfer-with-fee, mint and burn all resolve the auditor key through one function. This keeps the fetch-the-mint fallback rather than my version's zero-pubkey fallback, which would have silently produced proofs the program rejects for a mint with a configured auditor.U64_MAXcomparisons forassertU64Amount.assertMintBurnAmountstays: its 2^48−1 bound is specific to the lo/hi split used for the mint/burn range proof and has no analogue in the fee path.The mint/burn input types compose
ConfidentialTransferContextStateProofModeforpayer/rpc/proofMode, matching the transfer inputs, instead of redeclaring them.Testing
test/confidentialTransferArithmetic.test.ts— offline check thataddWithLoHiCiphertextsmatches a plaintext add.test/extensions/confidentialMintBurn/getConfidentialMintBurnInstructionPlan.test.ts— validator e2e: mint → apply → burn → apply-pending-burn → update-supply against the on-chain ZK verifiers.test/extensions/confidentialMintBurn/getPermissionedConfidentialBurnInstructionPlan.test.ts— confidential burn on aPermissionedBurnmint.Full suite green against a local validator (76 files / 149 tests), including #1253's transfer-with-fee test — worth noting since the auditor resolver is now shared by every proof path.
tsc --noEmit,pnpm lintandpnpm formatclean.